<!-- 下拉树 -->
<template>
  <div class="custom-tree-select" :class="selectClass">
    <!-- 下拉文本 -->
    <el-select
      :id="'custom-tree-select-' + guid"
      :style="styles"
      class="custom-tree-select-input"
      v-model="labels"
      :disabled="disabled"
      popper-class="select-option"
      ref="select"
      v-bind="selectParams"
      :popper-append-to-body="false"
      :filterable="false"
      :multiple="selectParams.multiple"
      v-popover:popover
      @remove-tag="_selectRemoveTag"
      :title="labels"
      @clear="_selectClearFun"
      @focus="_popoverShowFun"
    />
    <!-- 弹出框 -->
    <el-popover
      ref="popover"
      :placement="placement"
      :transition="transition"
      :popper-class="popperClass"
      :width="width"
      v-model="visible"
      trigger="click"
    >
      <!-- 是否显示搜索框 -->
      <el-input
        v-if="treeParams.filterable"
        v-model="keywords"
        size="mini"
        class="input-with-select mb10"
        @change="_searchFun"
      >
        <el-button slot="append" icon="el-icon-search"></el-button>
      </el-input>
      <div>
        <!-- 树列表 -->
        <el-tree
          ref="tree"
          v-show="data.length > 0"
          v-bind="treeParams"
          :load="loadNode"
          lazy
          :node-key="'id'"
          :draggable="false"
          :current-node-key="ids.length > 0 ? ids[0] : ''"
          :show-checkbox="false"
          :filter-node-method="filterNodeMethod ? filterNodeMethod : _filterFun"
          :render-content="treeRenderFun"
          @node-click="_treeNodeClickFun"
          @check="_treeCheckFun"
        />
        <!-- 暂无数据 -->
        <div v-if="data.length === 0" class="no-data">暂无数据</div>
      </div>
    </el-popover>
  </div>
</template>

<script>
import { on, off, each, guid } from './TreeUtils'
import { getMajorList } from '@/api/business'
import { Message } from 'element-ui'
// @group api
export default {
  name: 'TreeSelect',
  props: {
    // v-model,存储的是treeParams.data里面的id
    value: {
      // `String` / `Array` / `Number`
      type: [String, Array, Number],
      // `''`
      default() {
        return ''
      }
    },
    // el-select样式
    styles: {
      type: Object,
      // {}
      default() {
        return {}
      }
    },
    // 下拉框 挂类
    selectClass: {
      type: String,
      default() {
        return ''
      }
    },
    // popover 挂类
    popoverClass: {
      type: String,
      default() {
        return ''
      }
    },
    // 是否禁用文本框
    disabled: {
      type: Boolean,
      // false
      default() {
        return false
      }
    },
    // 弹出框位置
    placement: {
      type: String,
      //  bottom
      default() {
        return 'bottom'
      }
    },
    // 弹出框过渡动画
    transition: {
      type: String,
      //  el-zoom-in-top
      default() {
        return 'el-zoom-in-top'
      }
    },
    // 树渲染方法，具体参考el-tree Function(h, { node, data, store }) {}
    treeRenderFun: Function,
    // 搜索过滤方法，具体参考el-tree Function(h, { value, data, node }) {}
    filterNodeMethod: Function,
    /*
    文本框参数，几乎支持el-select所有的API<br>
    取消参数：<br>
    设定下拉框的弹出框隐藏：<br>
    `:popper-append-to-body="false"` <br>
    搜索从弹出框里面执行： <br>
    `filterable="false"`
    */
    selectParams: {
      type: Object,
      /*
      Object默认参数：<br><br>
      是否可以清空选项：<br>
      `clearable: true,`<br><br>
      是否禁用：<br>
      `disabled: false,`<br><br>
      搜索框placeholder文字：<br>
      `placeholder: '请选择',`<br><br>
      */
      default() {
        return {
          clearable: true,
          disabled: false,
          placeholder: '请选择'
        }
      }
    },
    /*
    下拉树参数，几乎支持el-tree所有的API<br>
     取消参数:<br>
    `:show-checkbox="selectParams.multiple"`<br>
    使用下拉框参数multiple判断是否对树进行多选<br>
    取消对el-tree的人为传参show-checkbox<br>
    `:node-key="propsValue"`     自动获取treeParams.props.value<br>
    `:draggable="false"`         屏蔽拖动
    */
    treeParams: {
      type: Object,
      /*
      Object默认参数：<br><br>
      在有子级的情况下是否点击父级关闭弹出框,false 只能点击子级关闭弹出框：<br><br>
      `clickParent: false`<br><br>
      是否显示搜索框：<br><br>
      `filterable: false`<br><br>
      是否只是叶子节点：<br><br>
      `leafOnly: false`<br><br>
      是否包含半选节点：<br><br>
      `includeHalfChecked: false`<br><br>
      下拉树的数据：<br><br>
      `data:[]`<br><br>
      下拉树的props：<br><br>
      `props: {`<br>
          `children: 'children',`<br>
          `label: 'name',`<br>
          `value: 'flowId',`<br>
          `disabled: 'disabled'`<br>
      `}`
      */
      default() {
        return {
          clickParent: false,
          filterable: false,
          leafOnly: false,
          includeHalfChecked: false,
          data: [],
          props: {
            children: 'children',
            label: 'name',
            code: 'code',
            value: 'flowId',
            disabled: 'disabled'
          }
        }
      }
    }
  },
  data() {
    return {
      guid: guid(),
      propsValue: 'id',
      propsLabel: 'name',
      propsCode: null, // 可能有空的情况
      propsDisabled: 'disabled',
      propsChildren: 'children',
      leafOnly: false,
      includeHalfChecked: false,
      data: [],
      keywords: '',
      labels: '', // 存储名称，用于下拉框显示内容
      ids: [], // 存储id
      visible: false, // popover v-model
      width: 150
    }
  },
  watch: {
    ids: function (val) {
      if (val !== undefined) {
        this.$nextTick(() => {
          this._setSelectNodeFun(val)
        })
      }
    },
    value: function (val) {
      if (this.ids !== val) {
        this._setMultipleFun()
        if (this.selectParams.multiple) {
          this.ids = [...val]
        } else {
          this.ids = val === '' ? [] : [val]
        }
      }
    }
  },
  computed: {
    popperClass() {
      let _c = 'custom-tree-select-popper ' + this.popoverClass + ' tree-select-popover'
      return this.disabled ? _c + ' disabled ' : _c
    }
  },
  created() {
    const { props, leafOnly, includeHalfChecked } = this.treeParams
    this._setMultipleFun()
    this.propsValue = props.value || 'id'
    this.propsLabel = props.label
    this.propsCode = props.code || null // 可能为空
    this.propsDisabled = props.disabled
    this.propsChildren = props.children
    this.leafOnly = leafOnly
    this.includeHalfChecked = includeHalfChecked

    if (this.selectParams.multiple) {
      this.labels = []
      this.ids = this.value
    } else {
      this.labels = ''
      this.ids = this.value instanceof Array ? this.value : [this.value]
      this.$nextTick(() => {
        this._setSelectNodeFun(this.ids)
      })
    }
  },
  mounted() {
    this._updateH()
    this.$nextTick(() => {
      on(document, 'mouseup', this._popoverHideFun)
    })
  },
  methods: {
    // 根据类型判断单选，多选
    _setMultipleFun() {
      let multiple = false
      if (this.value instanceof Array) {
        multiple = true
      }
      this.$set(this.selectParams, 'multiple', multiple)
    },
    // 输入文本框输入内容抛出
    _searchFun() {
      /*
      对外抛出搜索方法，自行判断是走后台查询，还是前端过滤<br>
      前端过滤：this.$refs.treeSelect.$refs.tree.filter(value)<br>
      后台查询：this.$refs.treeSelect.treeDataUpdateFun(data)
      */
      this.$emit('searchFun', this.keywords)
    },
    //  根据id筛选当前树名称，以及选中树列表
    _setSelectNodeFun(ids) {
      const el = this.$refs.tree
      if (!el) {
        throw new Error('找不到tree dom')
      }
      const { multiple } = this.selectParams
      // 长度为0，清空选择
      if (ids.length === 0 || this.data.length === 0) {
        this.labels = multiple ? [] : ''
        if (multiple) {
          el.setCheckedKeys([])
        } else {
          el.setCurrentKey(null)
        }
        return
      }
      if (multiple) {
        // element-ui bug. 如果是父子节点全选 el.setCheckedKeys([非全量id])之后el.getCheckedNodes()还是全量
        el.getCheckedNodes(this.leafOnly, this.includeHalfChecked).forEach((item) => {
          el.setChecked(item, false)
        })
        ids.forEach((id) => {
          el.setChecked(id, true)
        })
        const nodes = el.getCheckedNodes(this.leafOnly, this.includeHalfChecked)
        if (this.propsCode) {
          // 如果有code   labels=code(name)
          this.labels =
            nodes.map((item) =>
              item[this.propsCode] ? item[this.propsLabel] + '(' + item[this.propsCode] + ')' : item[this.propsLabel]
            ) || []
        } else {
          this.labels = nodes.map((item) => item[this.propsLabel]) || []
        }
      } else {
        el.setCurrentKey(ids[0])
        const node = el.getCurrentNode()
        if (node) {
          if (this.propsCode) {
            // 如果有code   labels=code(name)
            this.labels = node[this.propsCode]
              ? node[this.propsLabel] + '(' + node[this.propsCode] + ')'
              : node[this.propsLabel]
          } else {
            this.labels = node[this.propsLabel]
          }
        } else {
          this.labels = ''
        }
      }
      this._updatePopoverLocationFun()
    },
    // 更新popover位置
    _updatePopoverLocationFun() {
      // dom高度还没有更新，做一个延迟
      setTimeout(() => {
        this.$refs.popover.updatePopper()
      }, 50)
    },
    // 获取MouseEvent.path 针对浏览器兼容性兼容ie11,edge,chrome,firefox,safari
    _getEventPath(evt) {
      const path = (evt.composedPath && evt.composedPath()) || evt.path
      const target = evt.target
      if (path != null) {
        return path.indexOf(window) < 0 ? path.concat(window) : path
      }
      if (target === window) {
        return [window]
      }
      function getParents(node, memo) {
        memo = memo || []
        const parentNode = node.parentNode
        if (!parentNode) {
          return memo
        } else {
          return getParents(parentNode, memo.concat(parentNode))
        }
      }
      return [target].concat(getParents(target), window)
    },
    // 树过滤
    _filterFun(value, data, node) {
      if (!value) return true
      return data[this.propsLabel].indexOf(value) !== -1
    },
    // 树点击
    _treeNodeClickFun(data, node, vm) {
      const { multiple } = this.selectParams
      const { clickParent } = this.treeParams
      const checkStrictly = this.treeParams['check-strictly']
      const { propsValue, propsChildren, propsDisabled } = this
      const children = data[propsChildren] || []
      if (data[propsDisabled]) {
        // 禁用
        return
      }
      if (node.checked) {
        const value = data[propsValue]
        this.ids = this.ids.filter((id) => id !== value)
        if (!checkStrictly && children.length) {
          children.forEach((item) => {
            this.ids = this.ids.filter((id) => id !== item[propsValue])
          })
        }
      } else {
        if (!multiple) {
          // 单选，判断是否允许点击父级关闭弹出框
          if (!clickParent) {
            // 如果不允许点击父级,自身为末级，允许点击之后关闭
            if (data.typeClassify === 2) {
              this.ids = [data[propsValue]]
              this.visible = false
            } else {
              // 不允许父级，阻止继续派发
              return false
            }
          } else {
            this.ids = [data[propsValue]]
            this.visible = false
          }
        } else {
          // 多选，不关闭
          if (!clickParent && children.length === 0) {
            // 如果不能点击父级
            this.ids.push(data[propsValue])
          } else if (clickParent) {
            // 允许点击父级
            this.ids.push(data[propsValue])
            // 如果父子关联，将子节点push进勾选项
            if (!checkStrictly && children.length) {
              children.forEach((item) => {
                this.ids.push(item[propsValue])
              })
            }
          }
        }
      }
      this._emitFun()
      /*
      点击节点，对外抛出   `data, node, vm`<br>
      `data:` 当前点击的节点数据<br>
      `node:` 当前点击的node<br>
      `vm:` 当前组件的vm
      */
      this.$emit('node-click', data, node, vm)
    },
    // 树勾选
    _treeCheckFun(data, node, vm) {
      this.ids = []
      const { propsValue } = this
      node.checkedNodes.forEach((item) => {
        this.ids.push(item[propsValue])
      })
      /*
      点击复选框，对外抛出   `data, node, vm`<br>
      `data:` 当前点击的节点数据<br>
      `node:` 当前点击的node<br>
      `vm:` 当前组件的vm
      */
      this.$emit('check', data, node, vm)
      this._emitFun()
    },
    // 下拉框移除tag时触发
    _selectRemoveTag(tag) {
      const { data, propsValue, propsLabel, propsChildren } = this
      each(
        data,
        (item) => {
          if (item[propsLabel] === tag) {
            const value = item[propsValue]
            this.ids = this.ids.filter((id) => id !== value)
          }
        },
        propsChildren
      )
      this.$refs.tree.setCheckedKeys(this.ids)
      this.$emit('removeTag', this.ids, tag)
      this._emitFun()
    },
    // 下拉框清空数据
    _selectClearFun() {
      this.ids = []
      const { multiple } = this.selectParams
      // 下拉框清空，对外抛出``this.$emit('input', multiple ? [] : '')`
      this.$emit('input', multiple ? [] : '')
      // 下拉框清空，对外抛出``this.$emit('select-clear')`
      this.$emit('select-clear')
      this._updatePopoverLocationFun()
    },
    // 判断类型，抛出当前选中id
    _emitFun() {
      const { multiple } = this.selectParams
      this.$emit('input', multiple ? this.ids : this.ids.length > 0 ? this.ids[0] : '')
      this._updatePopoverLocationFun()
    },
    // 更新宽度
    _updateH() {
      this.$nextTick(() => {
        this.width = this.$refs.select.$el.getBoundingClientRect().width
      })
    },
    // 显示弹出框的时候容错，查看是否和el宽度一致
    _popoverShowFun(val) {
      this._updateH()
    },
    // 判断是否隐藏弹出框
    _popoverHideFun(e) {
      const path = this._getEventPath(e)
      let isInside = path.some((list) => {
        // 鼠标在弹出框内部，阻止隐藏弹出框
        return (
          list.className && typeof list.className === 'string' && list.className.indexOf('custom-tree-select') !== -1
        )
      })
      if (!isInside) {
        this.visible = false
      }
    },
    /**
     * @vuese
     * 树列表更新数据
     * @arg Array
     */
    treeDataUpdateFun(data) {
      this.data = data
      // 数据更新完成之后，判断是否回显内容
      if (data.length > 0) {
        setTimeout(() => {
          this._setSelectNodeFun(this.ids)
        }, 300)
      }
    },
    /**
     * @vuese
     * 本地过滤方法
     * @arg String
     */
    filterFun(val) {
      this.$refs.tree.filter(val)
    },
    loadNode(node, resolve) {
      const params = node.level === 0 ? {} : { parentId: node.data.id }
      getMajorList(params)
        .then((res) => {
          if (node.level === 0) {
            this.data = res.data
          }
          resolve(res.data)
        })
        .catch(() => {})
    }
  },
  components: {},
  beforeDestroy() {
    off(document, 'mouseup', this._popoverHideFun)
  }
}
</script>
<style>
.custom-tree-select {
  width: 100%;
  font-family: 'microsoft yahei';
}
.custom-tree-select .select-option {
  display: none !important;
}
.custom-tree-select .el-select {
  width: 100%;
}

[aria-disabled='true'] > .el-tree-node__content {
  color: inherit !important;
  background: transparent !important;
  cursor: no-drop !important;
}

.custom-tree-select-popper {
  max-height: 400px;
  overflow: auto;
}

.custom-tree-select-popper.disabled {
  display: none !important;
}

.custom-tree-select-popper .el-button--small {
  width: 25px !important;
  min-width: 25px !important;
}

.custom-tree-select-popper[x-placement^='bottom'] {
  margin-top: 5px;
}
.tree-select-popover {
  font-family: 'microsoft yahei';
}
</style>
<style scoped>
.mb10 {
  margin-bottom: 10px;
}

.no-data {
  height: 32px;
  line-height: 32px;
  font-size: 14px;
  color: #cccccc;
  text-align: center;
}
</style>
